from: Brian Fitzgerald HAL Labs 18942 Dallas Perris, CA 92370 (c) 1987 HAL Labs This document may be freely distributed as long as this copyright notice goes with it! The Surgeon General has not determined that the following material is dangerous to your health. However... Chapter 4: 65816 instructions and LISA syntax A difference in syntax. The accepted definition of 65816 instruction addressing modes is a bit awkward. There are a few differences between what some users may be accustomed to as regards the syntax of some instructions, and the way that LISA does things. When the 6502 was designed, it was as a 8-bit processor; all the instructions work with 8-bit data through the registers (A,X and Y) and 8-bit (zero page and stack) and 16-bit (absolute) addresses. Since data size was smaller than address size (for absolute addresses), addresses had to be manipulated in two parts, low byte and high byte. So, if you had to initialize a zero page pointer to some immediate-value address, there had to be some way to tell the assembler to use the low and high order bytes of an address. In LISA, you would use: SCREEN equ $2000 ;start of //e hi-res screen SCINIT lda #SCREEN ;get low byte of address sta PTR lda /SCREEN ;get high byte of address sta PTR+1 So, here we use the '#' pound sign to specify the low byte of an immediate value, and the '/' slash sign to specify the high byte of an immediate value. (Some assemblers use #< and #> for low and high bytes). Okay, so no problem, right? Well, after a while (about 1981), 16-bit processors started to become very popular, and certain people started thinking about a 16-bit version of the 6502. So, now we have the 65816. The 65816 is unusual in that it directly executes the older 6502 programs directly, with no translation; and yet it is also a 16-bit part. It handles this "split-personality" with the aid of several internal flags that the programmer can manipulate. If the M (for memory/accumulator) flag is set (M=1) then the accumulator (and all memory fetch/stores) are 8-bit; if it is cleared (M=0) then we have a 16-bit accumulator. This can be confusing!! There are actually no 8 or 16-bit instructions per se; if you are in 16-bit mode, all the appropriate instructions are 16-bit. So, for example: sep #%00100000 ;set for 8-bit memory/acc lda #$1234 ;load 1-byte value #$34 rep #%00100000 ;set for 16-bit memory/acc lda #$1234 ;load 2-byte value #$1234 The first line sets 8-bit memory/accumulator mode; therefore, the next LDA instruction will load a 1-byte value into the accumulator. The third line sets 16-bit memory/accumulator mode; therefore, the last LDA instruction will load a 2-byte value into the accumulator. But, if you looked at the opcodes generated, both LDAs are the same! The 65816 chip knows what is 8-bit and what is 16-bit because it looks at the flags; but you have to remember. Finally, the point of this; when you are writing code, you would like to be able to look at a line of source code and say "Yes, I meant this to be 8-bit (or 16-bit) blah blah blah". So, we need some further identifiers; and this is where LISA really diverges from the standard. For 8-bit data we use: lda #$123456 ;get low byte $56 lda /$123456 ;get mid byte $34 lda ^$123456 ;get bank byte $12 for 8-bit immediate data and lda |$123456 ;get low word $3456 lda \$123456 ;get high word $0012 for 16-bit immediate data. The standard would have you use the exact same modifiers for both 8-bit and 16-bit mode! Further, you have to go through contortions to tell the assembler that you are in 8-bit or 16-bit mode. This would not be that bad, except that you can't just stay in 16-bit mode; if you want to do 8-bit manipulations (say, on character data), you have to go to 8-bit mode. According to programmers at Apple, 80% of their problems involved being in the wrong mode! Now, if you like that sort of thing (or if you are translating code from APW, for example), you can use semi-standard mnemonics, in that '#' and '^' will behave differently in 8-bit or 16-bit mode. You have to tell the assembler what mode(s) with new pseudo-ops: .sa ;set 8-bit memory/accumulator .la ;set 16-bit memory/accumulator .sx ;set 8-bit index registers .lx ;set 16-bit index registers You must also do the appropriate REP or SEP at the same time, because the .SA, .LA, .SX, and .LX pseudo-ops are instructions for the assembler only; if you do one without the other, your program will fail spectacularly when it reaches that point in execution. It was mainly for this reason that LISA diverged from the standard as far as it did (also, the standard is very wordy). As an example, you could do this, if you really wanted to: M = %00100000 ;memory/ACC bit X = %00010000 ;index registers bit TEST sep #M+X ;set for 8-bit ACC, index .sa ;tell LISA about short ACC .sx ;and short index lda #$23 ;get a byte sta ONEBYTE ;save a byte rep #M ;set for 16-bit ACC .la ;and long ACC now lda #$23 ;get a word (#$0023) sta TWOBYTE ;save a word It looks straightforward here, but gets confusing in a 1,000 line program. Also, there's a lot more typing to do to keep things straight. So, what are the differences between LISA and the so-called standard? First, there are various byte and word select operators for immediate values and constants. 8-bit LISA Standard Operation result #$01020304 #$01020304 select low byte 04 or #<$01020304 /$01020304 #>$01020304 select middle byte 03 ^$01020304 #^$01020304 select high byte 02 16-bit LISA Standard Operation result |$01020304 #$01020304 select low word 0304 or or #$01020304 #<$01020304 \$01020304 #^$01020304 select high word 0102 or ^$01020304 Further, LISA handles labels a little differently as well. In the 6502, there were two different kinds of addresses P zero page and absolute. In the 65816, there are three kinds P direct page (similar to zero page), absolute, and long. The standard is to let the size of the address tell the type, so that addresses from 0I255 are direct page, addresses from 256I65535 are absolute, and the rest (65536I16777215) are long. LISA, on the other hand, uses explicit declarators to set direct page, absolute, and long. LISA Standard Operation LABEL epz $23 LABEL equ $23 define direct-page label LABEL equ $1234 LABEL equ $1234 define absolute label LABEL eql $123456 LABEL equ $123456 define long label LISA has different equates for two reasons; 1) it is more precise, and 2) it handles conflicts with low-valued absolute and long addresses. This was not as much a problem with the 6502 as it is with the 65816, with it's multiple banks of 64K blocks (via the data and program bank registers) and moveable direct page. Consider the following: org $010000 ;start at bank $01, addr $0000 CODE lda #$02 ;set DBR to bank $02 pha plb ;stack value to DBR lda DATA rts DATA byt 5 DATA is in the same bank as CODE, so any reasonable assembler would say "Ah HA! Same bank, so use absolute". Unfortunately, we set the DBR (data bank register) to a different bank, so we end up fetching data from $02xxxx instead of $01xxxx. This is where several LISA pseudo-ops come into play. First, there is the FAR pseudo-op. If FAR is used on any label, it will automatically be given the LONG attribute, and any instructions referencing that will use long addressing modes. This way, you could do equates into bank 0 that were long easily, without having to do type coercion for every single reference (as the standard would have you do). For the case above, we could have added: far DATA ;declare DATA as long always after the first line, and that would have made all references to DATA as long. This is fine sometimes (and usually necessary if you're working with banks given you by the IIGS memory manager), but what if you know what banks you're in at certain times, and want to optimize some fetches that would be in the same bank. Example org $010000 ;start at bank $01, addr $0000 far DATA ;declare DATA as long always far DATA2 ;declare DATA2 as long always CODE lda #$02 ;set DBR to bank $02 pha plb ;stack value to DBR lda DATA ;will be LONG ref sta DATA2 ;will be LONG ref rts DATA byt 5 org $020000 ;in bank $02 DATA2 byt 12 Now, here we are loading from $01xxxx and saving at $02xxxx; but we know that the DBR is pointing at bank $02, and DATA2 is needlessly long. We could have not defined DATA2 as FAR, but then later we might have moved the DBR again and still want to have referenced DATA2 again. What to do? To fix this, we introduce a new pseudo ops; .DB. The action of this is to inform the assembler (if we know) what the value of the DBR register is. So, looking at our example again, we could do the following: org $010000 ;start at bank $01, addr $0000 CODE lda #$02 ;set DBR to bank $02 pha plb ;stack value to DBR .db $020000 ;tell LISA we're in bank $02 lda DATA ;will be LONG ref sta DATA2 ;will be ABS ref lda #$01 ;set DBR to bank $01 pha plb ;stack value to DBR .db $010000 ;tell LISA we're in bank $02 lda DATA ;will be ABS ref sta DATA2 ;will be LONG ref rts DATA byt 5 org $020000 ;in bank $02 DATA2 byt 12 Now, as you can see from reading the comments, LISA will keep track of the different segments, and generate ABSOLUTE or LONG instructions in context. Effective use of the .DB pseudo-op can save you lots of trouble. NOTE: the .DB instruction expects a long address, of which it uses the high-order byte only. This is so that you can use a label in the bank you're targeting with the .DB instruction; also, so that you can use the value of the PC counter, i.e. ".DBJ*", which would reset LISA's notion of where the DBR is to the current value of the PBR (program bank register). One useful trick when setting the DBR to point to a bank returned by the memory manager is to use .DB to tell LISA that you are in a bank which will never be referenced normally, like bank $FF (which contains sancrosanct ROM code on the IIGS). I.e. lda SymBank ;set DBR to symbol table bank pha plb ;stack value to DBR plb .db $FF0000 ;tell LISA we're in bank $FF lda (Sym) ;get symbol byte sta DATA ;and save it where here we load ABSOLUTE from a bank that we don't know the location of at assembly timeP so we tell LISA it's at a location we will never access normally. Note that you could NOT say ".DBJSYMBANK" because SYMBANK is a variable, not an assembly-time constant. Finally, there are every once in a while situations that don't fall into any of the above catagories, where you want to have some reference be ABSOLUTE or LONG where there is no way to tell the assembler other than by type coercion. Since there are already so many prefix characters for an expression (byte, word, negation etc), LISA prefers to use a suffix on the label P this also makes more sense, since you are coercing the label, not the expression (although the expression usually follows as a consequence). To do type coercion, you follow the label with a suffix like ":A" for ABSOLUTE and ":L" for LONG. So, we could do lda SymBank ;set DBR to symbol table bank pha plb ;stack value to DBR plb lda BYTES:A ;absolute ref sta DATA ;and save it if we knew that DATA was an offset from the start of the bank, say if we had allocated a whole 64K bank and were using absolute offsets into that bank (which can be a handy thing to do at times). So there are some more differences to categorize between LISA and the standard 65816 syntax. They are. LISA Standard Operation (not applicable) >Label force direct page Label:A |Label force absolute Label:L